package air2.totc.crypt;

import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

/**
 * Classe abstraite représentant un encodeur
 * Wrappe les méthodes d'encodage dans des méthodes abstract
 * Appelle ces méthodes avec les méthodes <final> encrypt et decrypt
 * @author tmz
 *
 */
public abstract class Encoder 
{
	protected Cipher c;
	public static final String DESString = "DES";
	public static final String AESString = "AES";
	public static final String RSAString = "RSA";

	/**
	 * Encode un tableau de bytes
	 * @param bOutData Chaîne à encoder
	 * @return Chaîne encodée
	 * @throws InvalidKeyException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchProviderException
	 * @throws NoSuchPaddingException
	 * @throws GeneralSecurityException 
	 */
	public abstract byte[] encrypt(byte bOutData[]) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, GeneralSecurityException;
	
	/**
	 * Décode un tableau de bytes
	 * @param bInData Chaîne à décoder
	 * @return Chaîne décodée
	 * @throws InvalidKeyException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchProviderException
	 * @throws NoSuchPaddingException
	 * @throws GeneralSecurityException 
	 */
	public abstract byte[] decrypt(byte bInData[]) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, GeneralSecurityException;
	
	/**
	 * Wrappe la méthode encrypt
	 * @param s chaîne à chiffre
	 * @return chaîne chiffrée, encodée en Base64
	 * @throws GeneralSecurityException 
	 */
	public final String encode(String s) throws GeneralSecurityException
	{
		byte encdata[] = org.spongycastle.util.encoders.Base64.encode(this.encrypt(	// Encryption
				s.getBytes())); // de la chaîne encodée en base64
		s = new String(encdata);
		return s;
	}
	
	/**
	 * Wrappe la méthode decode
	 * @param s Chaîne à décoder
	 * @return Chaîne décodée
	 * @throws GeneralSecurityException 
	 */
	public final String decode(String s) throws GeneralSecurityException
	{
		byte decdata[] =  this.decrypt(org.spongycastle.util.encoders.Base64.decode(s.getBytes()));	// de la chaîne décryptée
		s = new String(decdata);
		return s;
	}
	
	/**
	 * Fabrique une clé à partir d'une chaîne de longueur variable, en utilisant le SHA-512
	 * @param key
	 * @param length
	 * @return la clé
	 * @throws NoSuchAlgorithmException
	 */
	public final static byte[] padKeyToLength(byte key[], int length) throws NoSuchAlgorithmException
	{
		length/=8;	// En octets
		byte[] finalKey = new byte[length];
		byte[] hashedKey;
		MessageDigest sha = MessageDigest.getInstance("SHA-512");
		hashedKey = sha.digest(key);
		for(int i = 0; i < finalKey.length; i++)
		{
			finalKey[i] = hashedKey[i];
		}
		
		return finalKey;
	}
	
	public static final byte[] recoverArray(byte[][] data)
	{
		int length = 0, offset = 0;
		for(int i = 0; i < data.length; i++)
			length+=data[i].length;
		
		byte result[] = new byte[length];
		for(int i = 0; i < data.length; i++)
			for(int j = 0; j < data[i].length; j++)
			{
				result[offset] = data[i][j];
				offset++;
			}
		
		return result;
	}
	
	public static final byte[][] truncateArray(byte[] data, int maxLength)
	{
		String debug ="Fragments : ";
		// Calcul du nombre de fragments...
		int nbFragments = (int) Math.ceil(((double) data.length)/((double) maxLength));
		byte[][] result = new byte[nbFragments][];
		int offset = 0;
		for(int i = 0; i < nbFragments; i++)
		{
			int curLength = Math.min(maxLength, (data.length - offset));
			result[i] = new byte[curLength];
			for(int j = 0; j < curLength; j++)
			{
				result[i][j] = data[offset+j];
			}
			debug=debug+i+"["+curLength+"]   ";
			offset+=curLength;
		}
		//if(true) throw new Error(debug);
		return result;
	}
}
